home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 25 / Cream of the Crop 25.iso / disk / mt220.zip / SOURCE.ZIP / MT.CPP < prev    next >
C/C++ Source or Header  |  1997-04-05  |  35KB  |  1,053 lines

  1. /* Move To drive/directory.
  2.    Copyright 1996-7 Jason Hood
  3.    Started:  14 April, 1996.
  4.    Finished:  4 June.
  5.    Modified: 29 September to 1 November:
  6.      Changed treatment of previous directories;
  7.      Removed "-> sorting -> indexing -> done" from the scan;
  8.      Added the extension option;
  9.      Changed the nonexistant directory message;
  10.      Changed the [wr]long functions to #define;
  11.      Fixed a bug with find function dealing with star not finding a directory;
  12.      Added open functions to open a file and exit the program if unable;
  13.      Added a hardware error handler to ignore drive not ready;
  14.      Modified reverse & finddirs function;
  15.      Added "+" & "-" options for adding and removing directories.
  16.  
  17.    9 to 11 February, 1997:
  18.      Added "##" to the make the new extension permanent, default extension;
  19.      Added return codes;
  20.      Can accept a trailing (back)slash;
  21.      Can accept a partial and then exact path (eg. "mt w /not/scanned");
  22.      Added the directory structure file to the display;
  23.      Modified to work with MTMem 2.00;
  24.      Changed setdrive to distinguish between unavailable and invalid.
  25.  
  26.    2 March:
  27.      Corrected a bug dealing with trailing (back)slash.
  28.  
  29.    27 to 1 April:
  30.      More than two previous directories allowed.
  31.      Select a partial directory by number.
  32.      List matches using number 0.
  33.  
  34.    Will change drive as well as directory.
  35.    Allows use of slash ("/") as well as backslash ("\").
  36.    Can use multiple dots (eg. treats "..." as "..\..").
  37.    Previous directory can be selected by "mt;" (or "mt ;" if not so lazy).
  38.    Use more semicolons ("mt;;;") or a number ("mt;3") to select others.
  39.    Partial directory names, where searches always start from the root.
  40.    "mt @drives" will construct a directory structure file for drives. "mt @"
  41.    will update the directory structure file for drives already in the file.
  42.    "mt +[+][path]" will create and add path to the file. If the other plus is
  43.    present, it will move to the new path. "mt -path" will delete path and
  44.    remove it from the file (root is never deleted).
  45.    Default file is "mtdirs.dat" (with path specified via mtmem). It can be
  46.    changed using "mt #[#]ext ..." where "ext" is the new extension (ie.
  47.    "mtdirs.ext") and "..." are the normal options. The second '#' will make
  48.    the new extension permanent. If "ext" is omitted it will default to "dat".
  49.  
  50.    Path and previous directories are stored in memory, allocated by mtmem.
  51.  
  52.    Exit Status:
  53.      0 - Successful operation (including help & status)
  54.      1 - MTMem not loaded
  55.      2 - Unable to create/load the structure file
  56.      3 - Directory not found
  57.      4 - Cannot create directory
  58.      5 - Cannot delete directory
  59.  
  60.  
  61.    Acknowledgements: Tim Jones' WASTED.PAS for finding directories.
  62.              findmpx is a modified version of Ralf Brown's findtsrs.
  63.  
  64.    You are free to use this code, or a portion thereof, as long as an
  65.    appropriate acknowledgement is made.
  66.  
  67.    Questions, suggestions and comments to hoodj@topaz.cqu.edu.au.
  68. */
  69.  
  70. #include <dir.h>
  71. #include <dos.h>
  72. #include <string.h>
  73. #include <fstream.h>
  74. #include <iomanip.h>
  75. #include <ctype.h>
  76. #include <stdlib.h>
  77. #include <stdio.h>
  78. #include <errno.h>
  79. #include <limits.h>
  80.  
  81. #define version "2.20"
  82.  
  83. #define EXIT_SUCCESS 0
  84. #define EXIT_NOMTMEM 1
  85. #define EXIT_NOMTDIR 2
  86. #define EXIT_UNFOUND 3
  87. #define EXIT_NOCREAT 4
  88. #define EXIT_NOREMOV 5
  89.  
  90. char mtdirs[MAXPATH],            // Directory structure file
  91.      prevbuf[2*MAXDIR+1],        // Previous directories buffer
  92.      **prev;                // Previous directories pointers
  93. int  prevnum;                // Number of previous directories
  94.  
  95. char olddir[MAXDIR], newdir[MAXDIR];    // The current and new directories
  96.  
  97. char** dirs;                // Directories for each drive
  98. int dirnum;                // Number of directories on each drive
  99. const unsigned MaxDirs = 1200;        // Maximum number of directories/drive
  100.  
  101. int setdrive(char drive);        // Set the active drive
  102.  
  103. void expand(char* path);        // Expand the dots
  104. int  find(char* path);            // Try and find path
  105. int  partdir(int num, char* partial[], int which); // Find a partial dir.
  106. void reverse(char drive, const char* path);       // Reverse path
  107.  
  108. int  adddir(char* dir);            // Add a directory (tree)
  109. int  remdir(char* dir);            // Remove a directory (tree)
  110. void deldir();                // Delete directory
  111. void update(int operation);        // Update the directory structure file
  112. void copy(ifstream& is, ofstream& os, long size);    // Copy size bytes
  113.  
  114. void dirfile(char* drives);        // Create directory structure for drives
  115. void finddirs();            // Find directories
  116. int  finddirec(struct ffblk& dir, const char* name = "");   // Find dir. name
  117. long index(ofstream& os, char drive);    // Index and write drive's directories
  118. int  sort(const void* a, const void* b);// How the directories are sorted
  119. int  subs(const char* path);        // Number of directories in path
  120.  
  121. void help();                // Display help screen
  122.  
  123. // Modify mtdirs' extension to ext (ie. "mtdirs.dat" becomes "mtdirs.ext")
  124. #define modext(ext) strcpy(strrchr(mtdirs, '.')+1, ext)
  125.  
  126. const long zero = 0;            // Variable to simulate wlong(os, 0)
  127.  
  128. // Write a long variable to a file as a binary value (ie. four bytes)
  129. #define wlong(os, num) os.write((char*)&num, sizeof(long))
  130.  
  131. // Read a long variable from a file as a binary value (ie. four bytes)
  132. #define rlong(is, num) is.read((char*)&num, sizeof(long))
  133.  
  134.  
  135. // Open the mtdirs file for reading. If it fails, exit the program with an
  136. // error message.
  137. void open(ifstream& is, const char* name = mtdirs) {
  138.   is.open(name, ios::in | ios::binary);
  139.   if (is) return;
  140.   cout << "Unable to open \"" << name << "\".\n";
  141.   exit(EXIT_NOMTDIR);
  142. }
  143.  
  144. // Open the mtdirs file for writing. If it fails, exit the program with an
  145. // error message.
  146. void open(ofstream& os, const char* name = mtdirs) {
  147.   os.open(name, ios::out | ios::binary);
  148.   if (os) return;
  149.   cout << "Unable to create \"" << name << "\".\n";
  150.   exit(EXIT_NOMTDIR);
  151. }
  152.  
  153.  
  154. // Hardware error handler for drive not ready - just ignore it.
  155. int handler(int, int, int, int) {
  156.   hardretn(_HARDERR_IGNORE);        // Could become fail
  157.   return 0;
  158. }
  159.  
  160.  
  161. // Find the multiplex number associated with MTMem. Return -1 if not installed.
  162. int findmpx() {
  163.  
  164.   char far* sig;
  165.   union REGS regs;
  166.  
  167.   for (int mpx = 0; mpx <= 255; mpx++) {
  168.     regs.h.ah = mpx;
  169.     regs.h.al = 0;            // installation check
  170.     int86(0x2d, ®s, ®s);
  171.     if (regs.h.al == 0xff) {        // installed?
  172.       sig = (char far*)MK_FP(regs.x.dx, regs.x.di);
  173.       if (_fstrncmp("Adoxa   MTMem   ", sig, 16) == 0) return mpx;
  174.     }
  175.   }
  176.   return -1;
  177. }
  178.  
  179.  
  180. int main(int argc, char* argv[]) {
  181.  
  182.   int j;
  183.  
  184.   int mtmem;                // Multiplex number
  185.   if ((mtmem = findmpx()) == -1) {
  186.     cout << "You must run \"MTMem\" first.\n";
  187.     return EXIT_NOMTMEM;
  188.   }
  189.   union REGS regs;            // Get the address of the structure
  190.   regs.h.ah = mtmem;            // file and previous directories
  191.   regs.h.al = 0x10;
  192.   int86(0x2d, ®s, ®s);
  193.   unsigned prevend = regs.x.ax + MAXPATH+2*MAXDIR; // Memory limit
  194.   movedata(regs.x.dx, regs.x.ax, _DS, (unsigned)mtdirs, MAXPATH+2*MAXDIR+1);
  195.   prevnum = 0;
  196.   char* prevcnt = prevbuf;
  197.   while (*prevcnt) {            // Find the number of previous dirs
  198.     prevcnt = strchr(prevcnt, 0) + 1;
  199.     prevnum++;
  200.   }
  201.   prev = new char*[prevnum];
  202.   prevcnt = prevbuf;
  203.   for (j = 0; j < prevnum; j++) {    // Point to the previous dirs
  204.     prev[j] = prevcnt;
  205.     prevcnt = strchr(prevcnt, 0) + 1;
  206.   }
  207.  
  208.   harderr(handler);            // Ignore an unready drive
  209.  
  210.   olddir[0] = getdisk() + 'A';        // Retrieve the current directory
  211.   olddir[1] = ':';                      // getcwd & _getdcwd do the same thing
  212.   olddir[2] = '\\';            //  as these four statements, but they
  213.   getcurdir(0, olddir+3);        //  have more associated rigmarole
  214.  
  215.   if (argc == 1) {                      // No parameters
  216.     cout << "\n        Current directory = " << olddir;
  217.     if (prevnum) {
  218.       cout << "\n       Previous directory = " << prev[0];
  219.       for (j = 1; j < prevnum; j++)
  220.     cout << endl << setw(25) << (j+1) << " = " << prev[j];
  221.     }
  222.     cout << "\n"
  223.         "\n Directory structure file = " << mtdirs
  224.      << endl;
  225.     return EXIT_SUCCESS;
  226.   }
  227.  
  228.   char* &dir = argv[1];                 // Easier to type
  229.  
  230.   if (*dir == '?' || dir[1] == '?') {    // First or second character help
  231.     help();                //  (switch char of your choice)
  232.     return EXIT_SUCCESS;
  233.   }
  234.  
  235.   int moved = 0,            // Flag to stop processing com. line
  236.       exit_code = EXIT_SUCCESS;
  237.  
  238.   while (!moved) {
  239.  
  240.     if (*dir == '#') {            // A different directory structure file
  241.       int permanent = 0;
  242.       if (dir[1] == '#') {        // A permament change
  243.     ++dir;
  244.     permanent = 1;
  245.       }
  246.       if (dir[1] == 0) modext("dat");
  247.       else modext(dir+1);
  248.       if (permanent)
  249.     movedata(_DS, (unsigned)mtdirs, regs.x.dx, regs.x.ax, strlen(mtdirs)+1);
  250.       if (--argc == 1) return EXIT_SUCCESS;
  251.       argv++; dir = argv[1];
  252.     }
  253.  
  254.     switch (*dir) {
  255.       case '@':                // (Re)construct directory structure
  256.     dirfile(dir+1);
  257.     break;
  258.  
  259.       case '+':             // Add a directory (tree) to the file
  260.     moved = (dir[1] == '+');    // and create it if it doesn't exist
  261.     if (moved) dir++;        // Skip the second plus
  262.     if (!adddir(dir+1)) {
  263.       cout << "Unable to create \"" << newdir << "\".\n";
  264.       exit_code = EXIT_NOCREAT;
  265.     }
  266.     break;
  267.  
  268.       case '-':                // Remove a directory (tree) from the
  269.     if (dir[1])            // file and delete it, but don't allow
  270.       if (!remdir(dir+1)) {        // "mt -" in case of accident
  271.         cout << "Unable to remove \"" << newdir << "\".\n";
  272.         exit_code = EXIT_NOREMOV;
  273.       }
  274.     break;
  275.  
  276.       case ';':                // Use a previous directory
  277.     if (isdigit(dir[1])) {          // A number was specified
  278.       if ((j = atoi(dir+1)) == 0) j = INT_MAX;
  279.     }
  280.     else {                //  else count semicolons
  281.       for (j = 1; dir[j] == ';'; j++);
  282.     }
  283.     j--;
  284.     if (j >= prevnum) strcpy(newdir, olddir);
  285.     else strcpy(newdir, prev[j]);
  286.     moved = 1;
  287.     break;
  288.  
  289.       default:
  290.     moved = 1;
  291.     char* endp;
  292.     int which = (int)strtol(dir, &endp, 10);
  293.     if (*endp == ',' || *endp == '*') argv[1] = endp+1;
  294.     else {
  295.       which = 1;
  296.       if (argc == 2) if (find(dir)) {
  297.         moved = 2;            // Find the full path for prev. dirs.
  298.         break;
  299.       }
  300.     }
  301.     int exact = 0;
  302.     if (*argv[argc-1] == '/' || *argv[argc-1] == '\\') {
  303.       argc--;
  304.       exact = 1;
  305.     }
  306.     int result = partdir(argc-1, argv+1, which);
  307.     if (result) {
  308.       if (result == 1) cout << "No match found.\n";
  309.       else cout << char(result) << ": has not been scanned.\n";
  310.       return EXIT_UNFOUND;
  311.     }
  312.     if (which == 0) return EXIT_SUCCESS;
  313.     if (exact) {
  314.       strcat(newdir, strupr(argv[argc]));
  315.       find(newdir);
  316.     }
  317.     }
  318.     if (!moved) {            // No movement, so check next parameter
  319.       if (--argc == 1) return exit_code;// No more parameters, exit
  320.       argv++; dir = argv[1];        // Point to the next
  321.     }
  322.   }
  323.  
  324.   int result = setdrive(*newdir);
  325.   if (result) {
  326.     cout << *newdir << ((result == 1) ? ": is unavailable.\n" :
  327.                        ": is an invalid drive.\n");
  328.     return EXIT_UNFOUND;
  329.   }
  330.   if (chdir(newdir) == -1) {
  331.     cout << '\"' << newdir << "\" is an invalid path.\n";
  332.     setdrive(*olddir);
  333.     return EXIT_UNFOUND;
  334.   }
  335.   if (moved == 2) {            // Get the full path for below
  336.     newdir[2] = '\\';
  337.     getcurdir(0, newdir+3);
  338.   }
  339.  
  340.   if (strcmp(newdir, olddir)) {
  341.     int len = strlen(olddir)+1;
  342.     movedata(_DS, (unsigned)olddir, regs.x.dx, regs.x.ax+MAXPATH, len);
  343.     regs.x.ax += MAXPATH+len;
  344.     for (j = 0; j < prevnum; j++) {
  345.       if (strcmp(newdir, prev[j]) && strcmp(olddir, prev[j])) {
  346.     len = strlen(prev[j])+1;
  347.     if ((regs.x.ax + len) > prevend) break;
  348.     movedata(_DS, (unsigned)prev[j], regs.x.dx, regs.x.ax, len);
  349.     regs.x.ax += len;
  350.       }
  351.     }
  352.     // pokeb doesn't work right - doesn't seem to like regs.x.dx
  353.     *((char far*)MK_FP(regs.x.dx, regs.x.ax)) = 0;
  354.   }
  355.  
  356.   return EXIT_SUCCESS;
  357. }
  358.  
  359.  
  360. // Set the active drive to "drive" (which is assumed to be capital).
  361. // Return:
  362. //   0 for success;
  363. //   1 for unavailable drive (eg. no disc present);
  364. //   2 for invalid drive (eg. LASTDRIVE exceeded).
  365. int setdrive(char drive) {
  366.   drive -= 'A';
  367.   setdisk(drive);
  368.   if (getdisk() != drive) return 2;
  369.   if (chdir(".") == -1) {           // Simple test for disc present
  370.     setdisk(*olddir - 'A');        // Restore the old one if not
  371.     return 1;
  372.   }
  373.   return 0;
  374. }
  375.  
  376.  
  377. // Copy path to newdir, adding the current drive if no drive is specified,
  378. // replacing '/' with '\', expanding the dots at the start and removing the
  379. // trailing backslash.
  380. void expand(char* path) {
  381.  
  382.   if (path[1] == ':') {                 // A drive has been specified
  383.     *newdir = toupper(*path);           // Make sure it's uppercase
  384.     path += 2;                // Point past it
  385.   }
  386.   else *newdir = *olddir;        // Use current drive
  387.   newdir[1] = ':';
  388.  
  389.   if (*path == 0) {            // Only a drive has been specified
  390.     newdir[2] = '.';            // so select the current directory
  391.     newdir[3] = 0;            // on the new drive, as chdir("C:")
  392.     return;                // (for example) will not work
  393.   }
  394.  
  395.   int j = 2;
  396.   for (int dots = 1; *path == '.'; dots++, path++) {
  397.     if (dots > 2) {            // Expand the dots by adding "\."
  398.       newdir[j++] = '\\';        // at the appropriate positions
  399.       newdir[j++] = '.';        // (Eg. for "..." the first two are
  400.     }                    // copied, then "\." is added, then
  401.     newdir[j++] = '.';            // the third dot is copied, yielding
  402.   }                    // "..\..")
  403.  
  404.   int k = strlen(path)-1;
  405.   // Remove the ending (back)slash, but only if it's not the root
  406.   if ((k > 1 || j > 2) && (path[k] == '/' || path[k] == '\\')) path[k--] = 0;
  407.   for (; k >= 0; k--)
  408.     if (path[k] == '/') path[k] = '\\';
  409.  
  410.   strcpy(newdir+j, path);        // Copy the rest of the path
  411. }
  412.  
  413.  
  414. // See if path exists. It may end in a "*", in which case the first directory
  415. // that matches will be selected. If the path does not exist, but it was
  416. // expected to (parent shortcut, ending in star, or containing multiple paths)
  417. // then there is no need for a search, so say it exists anyway.
  418. // Returns 1 if path exists, 0 if not.
  419.  
  420. int find(char* path) {
  421.  
  422.   struct ffblk dir;
  423.   int star = (path[strlen(path)-1] == '*');    // End in star?
  424.  
  425.   expand(path);
  426.   if (finddirec(dir, newdir)) {        // The path doesn't exist
  427.     if (newdir[2] == '.' || star || strchr(newdir, '\\')) return 1;
  428.     return 0;
  429.   }
  430.  
  431.   if (star) {                // Expand the name found
  432.     for (int j = strlen(newdir)-2;    // Find where the name starts
  433.        j >= 0 &&            // The very beginning or
  434.        newdir[j] != '\\' &&
  435.        newdir[j] != ':';        // after the drive
  436.      j--);
  437.     strcpy(newdir+j+1, dir.ff_name);    // Replace with the actual name
  438.   }
  439.   return 1;
  440. }
  441.  
  442.  
  443. // From the partial names try and find the which'th match. If the first name
  444. // contains a drive specification then only that drive will be searched. If
  445. // which is 0 then a numbered list of all matches will be displayed.
  446. // Return 0 for success, 1 for no match, or the drive letter if that drive
  447. // has not been scanned.
  448.  
  449. int partdir(int num, char* partial[], int which) {
  450.  
  451.   ifstream mt; open(mt);
  452.  
  453.   char drive, drv,            // Drive to search, current drive
  454.        let,                // First letter to match
  455.        path[MAXDIR],            // A possible path
  456.        *dir;                // Subdirectory to match
  457.   long index, next = 0;            // File positions
  458.   int found,                // Pretty much self-explanatory
  459.       *partlen = new int[num];        // Lengths of the partial names
  460.  
  461.   for (int j = 0; j < num; j++)
  462.     partlen[j] = strlen(strupr(partial[j]));
  463.  
  464.   if (partial[0][1] == ':') {        // A drive has been specified
  465.     drive = *partial[0];        // so only search its directories
  466.     partial[0] += 2;            // Skip past it to the first name
  467.     partlen[0] -= 2;
  468.   }
  469.   else drive = 0;            // No drive, so search all of them
  470.  
  471.  
  472.   do {                    // For each drive required to search
  473.     mt.seekg(next);            // Point to the drive
  474.     mt.get(drv);            // Get this drive letter
  475.     rlong(mt, next);            // Pointer to next drive
  476.     if (drive) {            // Find the drive we want
  477.       while (next && drv != drive) {
  478.     mt.seekg(next);
  479.     mt.get(drv);
  480.     rlong(mt, next);
  481.       }
  482.       if (drv != drive) return drive;    // Drive not in file
  483.     }
  484.     if (isalpha(let = *partial[num-1])) {  // First name start with a letter?
  485.       mt.seekg((let-'A') * sizeof(long), ios::cur);
  486.       rlong(mt, index);            // Then find the appropriate position
  487.       if (index == 0) continue;        // No directories start with this letter
  488.       mt.seekg(index);            // Starting position
  489.     }
  490.     else mt.seekg(26 * sizeof(long), ios::cur);    // Start straight after indices
  491.  
  492.     mt.getline(path, MAXDIR);
  493.     while (let == *path) {
  494.       found = 0;            // Number of matches found
  495.       dir = path;            // First subdirectory
  496.       for (int j = num-1; j >= 0 &&
  497.               !strncmp(partial[j], dir, partlen[j]); j--) {
  498.     found++;
  499.     dir = strchr(dir, '/');        // Find the next subdirectory
  500.     if (!dir) break;         // The path has reached the root
  501.     dir++;                // Point past the slash
  502.       }
  503.       if (found == num && --which <= 0) {// A successful match
  504.     reverse(drv, path);        // Put the full path into newdir
  505.     if (which < 0) cout << -which << " = " << newdir << endl;
  506.     else
  507.       if (strcmp(newdir, olddir)) {    // Only successful if not already here
  508.         next = 0;            // To terminate the do loop
  509.         break;            // To terminate the while loop
  510.       }
  511.       else ++which;
  512.       }
  513.       mt.getline(path, MAXDIR);         // Try another match
  514.     }
  515.   } while (!drive && next);        // Drive not specified and more exist
  516.  
  517.   delete []partlen;
  518.  
  519.   return (which <= 0) ? 0 : 1;
  520. }
  521.  
  522.  
  523. // Reverse path into newdir. If drive is null then reverse a DOS path (\) into
  524. // mt's path (/), else add drive and reverse mt into DOS.
  525.  
  526. void reverse(char drive, const char* path) {
  527.  
  528.   char *beg,                // Where to place the current subdir.
  529.        sep1, sep2;            // The separators
  530.   int j, end = strlen(path)-1;        // Subdir's start and end
  531.  
  532.   if (drive) {
  533.     newdir[0] = drive;
  534.     newdir[1] = ':';
  535.     newdir[2] = '\\';
  536.     beg = newdir+3;
  537.     sep1 = '/'; sep2 = '\\';
  538.   }
  539.   else {
  540.     beg = newdir;
  541.     sep1 = '\\'; sep2 = '/';
  542.   }
  543.  
  544.   for (j = end-1; j > 0; j--) {        // Search backwards for a separator
  545.     if (path[j] == sep1) {        // Found one, so from here to the
  546.       memcpy(beg, path+j+1, end-=j);    // last one is the subdir
  547.       beg += end;
  548.       *(beg++) = sep2;
  549.       end = --j;            // Ready for the next
  550.     }
  551.   }
  552.   memcpy(beg, path, ++end);        // Final subdirectory
  553.   *(beg+end) = 0;
  554. }
  555.  
  556.  
  557. // Add a directory (tree) to the file and create it if necessary. Unlike md
  558. // this can create two directories in one hit. eg. "mt +this/that" is
  559. // equivalent to "md this" "md this\that". If path is an empty string then add
  560. // the current directory.
  561. // Returns 1 for success or 0 for unable to create.
  562.  
  563. int adddir(char* path) {
  564.  
  565.   char *sep = newdir+2,            // Current directory to create
  566.        *start = NULL,            // Start of the tree
  567.        orig[MAXDIR],            // Current path on another drive
  568.        *ex;                // To remember newdir
  569.   int success = 1;
  570.  
  571.   expand(path);
  572.  
  573.   if (*newdir != *olddir) {        // It's on a different drive
  574.     if (setdrive(*newdir)) return 0;    //  so change to it and
  575.     *orig = '\\';            //  remember its current directory
  576.     getcurdir(0, orig+1);
  577.   }
  578.   if (chdir(newdir) == -1) {        // It doesn't already exist
  579.     do sep = strchr(sep+1, '\\');    // Skip past the dot directories
  580.     while (*(sep-1) == '.');
  581.     while (sep) {            // Branch down the tree
  582.       *sep = 0;
  583.       if (mkdir(newdir) == 0) {
  584.     if (!start) start = strdup(newdir);
  585.       }
  586.       else if (errno == ENOENT) {    // Bad name
  587.     if (start) {                // Some directories were made
  588.       success = 0;
  589.       break;
  590.     }
  591.     else {
  592.       setdrive(*olddir);
  593.       return 0;
  594.     }
  595.       }
  596.       *sep = '\\';
  597.       sep = strchr(sep+1, '\\');
  598.     }
  599.     if (mkdir(newdir) == -1) {
  600.       if (!start) {
  601.     setdrive(*olddir);
  602.     return 0;
  603.       }
  604.       success = 0;
  605.     }
  606.     if (start) chdir(start);
  607.     else chdir(newdir);
  608.   }
  609.   ex = strdup(newdir);            // newdir corrupted by update
  610.   update(0);                // Add the directory/ies to the file
  611.   strcpy(newdir, ex);
  612.   free(ex);
  613.   if (*newdir != *olddir) {
  614.     chdir(orig);
  615.     setdrive(*olddir);
  616.   }
  617.   else chdir(olddir);
  618.   return success;
  619. }
  620.  
  621.  
  622. // Remove path from the file and delete it from disk if the first character
  623. // is not "-". ie. "mt -path" will delete path and remove it from the file;
  624. // "mt --path" will just remove path from the file. The root directory won't
  625. // be deleted in any case.
  626. // Returns 1 for success or 0 for doesn't exist / isn't a directory.
  627.  
  628. int remdir(char* path) {
  629.  
  630.   char *ex, orig[MAXDIR];        // Expanded path, other drive's dir.
  631.   int rem = (*path == '-'),        // Removing only, no deletion
  632.       root;
  633.  
  634.   if (rem) path++;            // Skip the leading minus
  635.  
  636.   expand(path);
  637.   if (*newdir != *olddir) {        // As for adddir
  638.     if (setdrive(*newdir)) return 0;
  639.     *orig = '\\';
  640.     getcurdir(0, orig+1);
  641.   }
  642.   if (chdir(newdir) == -1) {            // Change to the directory
  643.     setdrive(*olddir);            //  to be deleted
  644.     return 0;
  645.   }
  646.   ex = strdup(newdir);            // update corrupts newdir
  647.   update(1);                // Remove the directory from the file
  648.   getcurdir(0, newdir);            // Test for root directory
  649.   root = *newdir;
  650.   strcpy(newdir, ex);
  651.   free(ex);
  652.   if (!rem && root) deldir();        // Delete the directory from the disk
  653.   if (*newdir != *olddir) {          //  (but only if it's not the root)
  654.     chdir(orig);
  655.     setdrive(*olddir);
  656.   }
  657.   else chdir(olddir);
  658.   if (!rem && root) if (rmdir(newdir) == -1) adddir(newdir);
  659.   return 1;
  660. }
  661.  
  662.  
  663. // Delete ALL files and subdirectories in the current drive/directory.
  664. void deldir() {
  665.  
  666.   struct ffblk find;
  667.   static int done;
  668.  
  669.   done = findfirst("*.*", &find,
  670.            FA_ARCH | FA_HIDDEN | FA_RDONLY | FA_SYSTEM | FA_DIREC);
  671.   while (!done) {
  672.     if (find.ff_attrib & FA_DIREC) {
  673.       if (*find.ff_name != '.') {    // Why do these have to be found?
  674.     chdir(find.ff_name);
  675.     deldir();
  676.     chdir("..");
  677.     rmdir(find.ff_name);
  678.       }
  679.     }
  680.     else {
  681.       if (find.ff_attrib & FA_RDONLY) _dos_setfileattr(find.ff_name, 0);
  682.       unlink(find.ff_name);
  683.     }
  684.     done = findnext(&find);
  685.   }
  686. }
  687.  
  688.  
  689. // Update the directory structure file. Operation is 0 for adding directories,
  690. // 1 for removing.
  691. void update(int operation) {
  692.  
  693.   ifstream mtin; open(mtin);
  694.  
  695.   char drive = *newdir, drv;
  696.   long size, next = 0, last = 1, remdrive;
  697.  
  698.   do {                    // See if the drive is in the file
  699.     remdrive = last;            // For removing a drive
  700.     mtin.seekg(next);
  701.     mtin.get(drv);
  702.     last = mtin.tellg();        // For adding a drive
  703.     rlong(mtin, next);
  704.   } while (drv != drive && next);
  705.   if (drv != drive && operation == 1) {    // Drive not in file and I'm removing
  706.     mtin.close();            // so there's nothing to do
  707.     return;
  708.   }
  709.  
  710.   char *mttmp = strdup(mtdirs);        // Remember the filename
  711.   modext("$~$");            // and create a temporary file
  712.   ofstream mtout; open(mtout);
  713.  
  714.   size = (drv == drive ? mtin.tellg()-sizeof(long)-sizeof(char) :
  715.              (mtin.seekg(0, ios::end), mtin.tellg()));
  716.   mtin.seekg(0);            // Copy all the drives before the one
  717.   copy(mtin, mtout, size);        // that's being changed
  718.   mtin.seekg(sizeof(char) + 27*sizeof(long), ios::cur);
  719.  
  720.   char path[MAXDIR];
  721.   int root, start, num, j;
  722.  
  723.   getcurdir(0, path);            // Check for the root directory
  724.   root = *path;                // !root means root directory
  725.   if (drv != drive)
  726.     cout << "Adding drive " << drive << " to \"" << mttmp << "\" : ";
  727.   else if (!root) {
  728.     if (operation == 0)
  729.       cout << "Updating drive " << drive << " of \"" << mttmp << "\" : ";
  730.     else
  731.       cout << "Removing drive " << drive << " from \"" << mttmp << "\".\n";
  732.   }
  733.  
  734.   if (root || operation == 0) {        // No point in scanning if removing
  735.     dirs = new char*[MaxDirs];        // an entire drive
  736.     dirnum = 0;
  737.     finddirs();
  738.     if (!root || drv != drive) cout << dirnum << " directories.\n";
  739.     qsort((void*)dirs, dirnum, sizeof(dirs[0]), sort);
  740.   }
  741.  
  742.   if (drv != drive) {
  743.     long temp = mtout.tellp();        // Set the old last drive to point
  744.     mtout.seekp(last);            // to the new drive
  745.     wlong(mtout, temp);
  746.     mtout.seekp(temp);
  747.     last = index(mtout, drive);
  748.   }
  749.   else if (!root) {
  750.     mtin.seekg(next);            // Skip what was in there
  751.     last = (operation == 0 ? index(mtout, drive) : remdrive);
  752.   }
  753.   else {
  754.     num = dirnum;
  755.     mtin.getline(path, MAXDIR);
  756.     while (*path && *path < *dirs[0]) {    // Copy all the smaller directories
  757.       dirs[dirnum++] = strdup(path);
  758.       mtin.getline(path, MAXDIR);
  759.     }
  760.     start = 0;
  761.     while (*path && *path <= *dirs[num-1]) {    // Check these directories
  762.       for (j = start; j < num; j++) {
  763.     if (*path < *dirs[j]) j = num-1;    // It can't be in the list
  764.     else if (!strcmp(path, dirs[j])) {
  765.       start = j+1;                // If it matches then
  766.       break;                // skip all those before it
  767.     }
  768.       }
  769.       if (j == num) dirs[dirnum++] = strdup(path);    // Wasn't found
  770.       mtin.getline(path, MAXDIR);
  771.     }
  772.     while (*path) {            // Copy all the larger directories
  773.       dirs[dirnum++] = strdup(path);
  774.       mtin.getline(path, MAXDIR);
  775.     }
  776.     if (operation == 0) {        // Sort new and old
  777.       qsort((void*)dirs, dirnum, sizeof(dirs[0]), sort);
  778.       last = index(mtout, drive);
  779.     }
  780.     else {
  781.       dirs += num;            // Skip the directories to remove
  782.       dirnum -= num;
  783.       last = index(mtout, drive);    // Index the remainder
  784.       dirs -= num;            // Get them back for freeing
  785.       dirnum += num;            // (The others are free'd in index)
  786.       for (j = 0; j < num; j++) free(dirs[j]);
  787.     }
  788.   }
  789.  
  790.   if (!next) {                // This was the last drive
  791.     mtout.seekp(last);            // so indicate such (assuming that
  792.     wlong(mtout, zero);            // not all drives have been removed)
  793.   }
  794.   else {                // Copy all the other drives
  795.     // Adjustment for the new indices
  796.     long offset = mtout.tellp() - mtin.tellg();
  797.     while (next) {
  798.       mtin.get(drv);
  799.       mtout.put(drv);
  800.       rlong(mtin, next);
  801.       if (next) next += offset;
  802.       wlong(mtout, next);
  803.       for (j = 0; j < 26; j++) {
  804.     rlong(mtin, last);
  805.     if (last) last += offset;
  806.     wlong(mtout, last);
  807.       }
  808.       if (next) copy(mtin, mtout, next-mtin.tellg());
  809.       else {
  810.     last = mtin.tellg();        // The last drive is from here
  811.     mtin.seekg(0, ios::end);    // to the end of the file, since
  812.     next = mtin.tellg();        // next is zero.
  813.     mtin.seekg(last);
  814.     copy(mtin, mtout, next-last);
  815.     next = 0;            // To terminate the loop
  816.       }
  817.     }
  818.   }
  819.   delete []dirs;
  820.   mtin.close();
  821.   mtout.close();
  822.   unlink(mttmp);            // Delete the original file
  823.   rename(mtdirs, mttmp);        // and replace it with the updated one
  824.   strcpy(mtdirs, mttmp);        // Restore the name
  825.   free(mttmp);
  826. }
  827.  
  828.  
  829. // Copy size bytes from file is to file os. The copy is done in blocks - the
  830. // maximum memory can handle, up to 32k (INT_MAX).
  831. void copy(ifstream& is, ofstream& os, long size) {
  832.  
  833.   if (size <= 0) return;
  834.  
  835.   char *buffer;
  836.   int num = 1;                // Number of blocks to perform the copy
  837.   int block = (size > INT_MAX ? INT_MAX : int(size)); // (int to remove warning)
  838.  
  839.   while (!(buffer = new char[block])) {    // Determine the largest block size
  840.     num <<= 1;                // Halve it each time
  841.     block = (block >> 1) + 1;
  842.   }
  843.   for (int j = --num; j > 0; j--) {    // Copy the full-size blocks
  844.     is.read(buffer, block);
  845.     os.write(buffer, block);
  846.   }
  847.   block = int(size - num*block);     // Copy what's left over
  848.   is.read(buffer, block);        // (Again, int to remove the warning)
  849.   os.write(buffer, block);
  850.  
  851.   delete []buffer;
  852. }
  853.  
  854.  
  855. // Create the directory structure (see finddirs, index and sort for details).
  856. // If drives is an empty string then scan the drives already in the file,
  857. // otherwise scan those drives specified (invalid drives will be ignored).
  858.  
  859. void dirfile(char* drives) {
  860.  
  861.   char drv[26], orig[MAXDIR];        // Drives to scan, original dir.
  862.   int n = strlen(strupr(drives));    // Number of drives to scan
  863.   long last = 0;            // Last drive position
  864.  
  865.   if (n) {                // Specified drives
  866.     strcpy(drv, drives);
  867.     cout << "Creating";
  868.   }
  869.   else {                // Drives already there
  870.     ifstream mtin; open(mtin);
  871.     do {
  872.       mtin.seekg(last);
  873.       mtin.get(drv[n++]);
  874.       rlong(mtin, last);
  875.     } while (last);
  876.     mtin.close();
  877.     cout << "Updating";
  878.   }
  879.   cout << " \"" << mtdirs << "\".\n";
  880.  
  881.   ofstream mt; open(mt);
  882.  
  883.   dirs = new char*[MaxDirs];        // Create the array of directories
  884.   orig[0] = '\\';
  885.   for (int j = 0; j < n; j++) {
  886.     cout << "Drive " << drv[j] << ' ';
  887.     if (setdrive(drv[j])) {
  888.       cout << "will be ignored.\n";
  889.       continue;
  890.     }
  891.     getcurdir(0, orig+1);
  892.     chdir("\\");
  893.     dirnum = 0;
  894.     finddirs();                // Get all the directories
  895.     chdir(orig);
  896.     cout << "has " << dirnum << " directories.\n";
  897.     qsort((void*)dirs, dirnum, sizeof(dirs[0]), sort);
  898.     last = index(mt, drv[j]);
  899.   }
  900.   mt.seekp(last);            // Write zero for last drive
  901.   wlong(mt, zero);
  902.   mt.close();
  903.   delete []dirs;
  904.  
  905.   setdrive(*olddir);            // Restore current drive
  906.   chdir(olddir);            // and directory
  907. }
  908.  
  909.  
  910. // Starting from the current directory recursively find all directories for
  911. // the current drive and store them as a reverse path separated by slashes.
  912. // eg: "\language\bc" will be stored as "bc/language".
  913. // Global variables dirs holds the directories; dirnum has the number found.
  914.  
  915. void finddirs() {
  916.  
  917.   struct ffblk dir;
  918.   static int done;
  919.   static char path[MAXDIR];
  920.  
  921.   getcurdir(0, path);
  922.   if (*path) {                // Don't store the root directory
  923.     reverse(0, path);            // (it's a null string)
  924.     dirs[dirnum++] = strdup(newdir);
  925.   }
  926.   for (done = finddirec(dir, "*.*"); !done; done = finddirec(dir)) {
  927.     chdir(dir.ff_name);
  928.     finddirs();              // Get any subdirectories
  929.     chdir("..");            // Back to this one
  930.   }
  931. }
  932.  
  933.  
  934. // Find a directory that matches name, ignoring the . and .. directories.
  935. // If name is not given, continue the find.
  936. // Returns the same as findfirst/findnext.
  937.  
  938. int finddirec(struct ffblk& dir, const char* name) {
  939.  
  940.   int found;
  941.  
  942.   found = (*name ? findfirst(name, &dir, FA_DIREC) : findnext(&dir));
  943.  
  944.   while (found == 0 && (dir.ff_attrib != FA_DIREC || *dir.ff_name == '.'))
  945.     found = findnext(&dir);
  946.  
  947.   return found;
  948. }
  949.  
  950.  
  951. // Write and index the directories for drive. First write the drive letter and
  952. // an index to the next drive. Following this are 26 indices for each letter of
  953. // the alphabet. An index of zero means no directories start with that letter.
  954. // Then the directories, one per line, and a blank line to finish.
  955. // It returns the position of the next drive index so zero can be written to
  956. // indicate no more drives. However, os is left at the end of the file.
  957.  
  958. long index(ofstream& os, char drive) {
  959.  
  960.   char let = 'A';            // Current index letter
  961.   long index, lin, din;            // File positions
  962.  
  963.   os.put(drive);            // Write the drive letter
  964.   din = os.tellp();            // The next drive index position
  965.   wlong(os, zero);            // Where the next drive will start
  966.   lin = os.tellp();            // The current letter index position
  967.   for (int j = 0; j < 26; j++) wlong(os, zero);    // Letters' index positions
  968.  
  969.   for (j = 0; j < dirnum; j++) {
  970.     while (*dirs[j] > let) {        // In case there are no directories
  971.       let++;                            // that start with let
  972.       lin += sizeof(long);
  973.     }
  974.     if (*dirs[j] == let) {        // Write the index for this letter
  975.       index = os.tellp();        // This is where it begins
  976.       os.seekp(lin);            // Go to letter index position
  977.       wlong(os, index);            // Write the value
  978.       os.seekp(0, ios::end);        // Back to the end
  979.       let++;                // Next letter
  980.       lin += sizeof(long);        // and its index position
  981.     }
  982.     os << dirs[j] << endl;        // Write the directory
  983.     free(dirs[j]);            // No longer required
  984.   }
  985.   os << endl;                // No more directories
  986.   index = os.tellp();                   // Store the position for next drive
  987.   os.seekp(din);
  988.   wlong(os, index);
  989.   os.seekp(0, ios::end);        // Ready for next drive
  990.   return din;                // For writing 0 for no more drives
  991. }
  992.  
  993.  
  994. // This determines how the directories will be searched. Smaller paths are
  995. // placed before larger paths, non-alphabetical entries are before alphabetical
  996. // directories, and the directories are sorted alphabetically.
  997.  
  998. int sort(const void *a, const void *b) {
  999.  
  1000.   char *dir1 = *(char**)a,              // qsort thinks I am sorting an array
  1001.        *dir2 = *(char**)b;        // of pointers - this gets the string
  1002.  
  1003.   if (*dir1 == *dir2) {            // Both start with the same character
  1004.     int len1 = subs(dir1),        // so sort by path length
  1005.     len2 = subs(dir2);
  1006.     if (len1 < len2) return -1;         // Same size path length will be
  1007.     else if (len1 > len2) return 1;    // sorted by name below
  1008.   }
  1009.   int let1 = isalpha(*dir1),        // First directory is alphabetic
  1010.       let2 = isalpha(*dir2);        // Second directory is alphabetic
  1011.   if ((let1 && let2) || (!let1 && !let2)) // Both are/are not letters
  1012.     return strcmp(dir1, dir2);        // so sort by name
  1013.  
  1014.   return (let1 ? 1 : -1);        // Letters placed after non-letters
  1015. }
  1016.  
  1017. // Determine the number of directories in a path for the sort routine.
  1018. int subs(const char* path) {
  1019.   int n = 0;                               // Number of slashes
  1020.   for (; *path; path++) if (*path == '/') n++;
  1021.   return n+1;                // Number of paths
  1022. }
  1023.  
  1024.  
  1025. // Display the help screen.
  1026. void help() {
  1027.   cout <<                // One really long string
  1028.   "\n"
  1029.   "mt - Move To drive/directory. mtmem - resident memory for mt.\n"
  1030.   "Copyright 1996-7 Jason Hood. Freeware. Version "version".\n"
  1031.   "\n"
  1032.   "mt           Display the current status.\n"
  1033.   "mt #[#]ext   Use file \"mtdirs.ext\" for the directory structure\n"
  1034.   "              [and make it permament].\n"
  1035.   "mt @dc       Create directory structure for drives D: and C:.\n"
  1036.   "mt @         Update directory structure (rescan drives).\n"
  1037.   "mt ....      Equivalent to \"mt ..\\..\\..\".\n"
  1038.   "mt;[;...|n]  Move to a previous directory (n = 0 is current directory).\n"
  1039.   "mt wg*       Move to first subdirectory starting with \"wg\".\n"
  1040.   "mt w/g       Move to subdirectory \"w\" subsubdirectory \"g\".\n"
  1041.   "mt wg        Move to subdirectory \"wg\". If that fails search\n"
  1042.   "              for a directory that starts with \"wg\".\n"
  1043.   "mt w g       Search for a directory starting with \"w\" that\n"
  1044.   "              has a subdirectory starting with \"g\".\n"
  1045.   "mt w g /h    As above, then append subdirectory \"h\".\n"
  1046.   "mt n,partial Select the nth match (or n+1 if already in nth).\n"
  1047.   "mt +         Add the current directory and its subdirs to the file.\n"
  1048.   "mt +[+]path  Create path if necessary, add it to the file [and move to it].\n"
  1049.   "mt -[-]path  Remove path from the file [but don't delete it from disk].\n"
  1050.   "\n"
  1051.   "Searches always begin from the root directory.\n";
  1052. }
  1053.